package com.lanzuo.pana.adapter.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.lanzuo.adapter.common.util.LooseJsonComparator;
import com.lanzuo.pana.adapter.config.FileWatcherProperties;
import com.lanzuo.pana.adapter.config.SpringContextHolder;
import com.lanzuo.pana.adapter.constant.OneCommandConstant;
import com.lanzuo.pana.adapter.record.domain.PanelRelationReportRecord;
import com.lanzuo.pana.adapter.record.service.PanelRelationReportRecordService;
import com.lanzuo.pana.adapter.util.SpiFileParsePanelUtil;
import com.lanzuo.pana.adapter.util.SpiFileParseUtil;
import io.micrometer.core.instrument.util.StringEscapeUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author eric
 * 文件监听服务
 */
@Slf4j
@Service
public class FileWatcherService {
    private static String MES_BACKUP_DIR = "D:\\Mes_backup\\";

    private final AtomicReference<Path> latestFile = new AtomicReference<>();
    // 内容级去重（防止重复发送相同连板码）
    private final Set<String> processedContentHashes = ConcurrentHashMap.newKeySet();
    // 创建只有一个线程的线程池
    ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
    @Resource
    MesInterface mesInterface;
    private WatchService watchService;
    private volatile boolean running = true;
    @Resource
    private FileWatcherProperties props;
    @Resource
    private PanelRelationReportRecordService panelRelationReportRecordService;

    @PostConstruct
    public void startWatching() {
        if (!props.isEnableWatch()) {
            log.info("文件监控已禁用");
            return;
        }

        Path dir = Paths.get(props.getDirectory()).toAbsolutePath().normalize();
        if (!Files.exists(dir)) {
            log.error("监控目录不存在: {}", dir);
            return;
        }

        try {
            watchService = FileSystems.getDefault().newWatchService();
            dir.register(watchService,
                    StandardWatchEventKinds.ENTRY_CREATE,
                    StandardWatchEventKinds.ENTRY_MODIFY);

            // 扫描已有文件，初始化 latestFile
            scanExistingFiles(dir);

            Thread watcherThread = new Thread(this::watchLoop, "FileWatcherThread");
            watcherThread.setDaemon(true);
            singleExecutor.execute(watcherThread);
            log.info("✅ 文件监控已启动，目录: {}", dir);
        } catch (IOException e) {
            log.error("❌ 启动文件监控失败", e);
        }
    }

    private void scanExistingFiles(Path dir) {
        try (Stream<Path> paths = Files.list(dir)) {
            Optional<Path> latest = paths
                    .filter(Files::isRegularFile)
                    .filter(this::matchesExtension)
                    .max(Comparator.comparing(this::getLastModifiedTimeSafe));
            latest.ifPresent(latestFile::set);
        } catch (IOException e) {
            log.warn("扫描已有文件失败", e);
        }
    }

    /**
     * 获取当前文件夹中所有文件的最新日期
     *
     * @param path
     * @return
     */
    private long getLastModifiedTimeSafe(Path path) {
        try {
            return Files.getLastModifiedTime(path).toMillis();
        } catch (IOException e) {
            return 0;
        }
    }

    /**
     * 循环监听文件夹
     */
    private void watchLoop() {
        while (running) {
            try {
                WatchKey key = watchService.take();
                for (WatchEvent<?> event : key.pollEvents()) {
                    if (event.kind() == StandardWatchEventKinds.OVERFLOW) continue;

                    @SuppressWarnings("unchecked")
                    WatchEvent<Path> ev = (WatchEvent<Path>) event;
                    Path filename = ev.context();
                    Path dir = Paths.get(props.getDirectory()).toAbsolutePath().normalize();
                    Path fullPath = dir.resolve(filename).toAbsolutePath().normalize();

                    if (Files.isRegularFile(fullPath) && matchesExtension(fullPath)) {
                        latestFile.set(fullPath);
                        log.info("📥 检测到文件变更: {}", fullPath.getFileName());
                        report2MesV2(fullPath);
                    }
                }
                if (!key.reset()) break;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
                if (running) {
                    log.error("文件监控线程异常", e);
                }
            }
        }
    }

    /**
     * 匹配后缀
     *
     * @param path 文件
     * @return 是否匹配
     */
    private boolean matchesExtension(Path path) {
        String ext = props.getFileExtension().toLowerCase();
        return ext.isEmpty() || path.toString().toLowerCase().endsWith(ext);
    }

    /**
     * 安全读取最新文件内容（已去重、已验证稳定）
     */
    public Optional<String> readLatestTrustedContent() {
        return Optional.ofNullable(latestFile.get())
                .filter(Files::exists)
                .map(this::readStableContent)
                .filter(content -> StrUtil.isNotBlank(content) && !content.trim().isEmpty())
                .map(String::trim)
                .filter(this::isNotProcessed);
    }

    /**
     * 待文件稳定（文件内容全部写完成）后读取文件内容
     *
     * @param path 文件路径
     * @return 文件内容
     */
    private String readStableContent(Path path) {
        int attempts = props.getStabilityCheckAttempts();
        long interval = props.getStabilityCheckInterval();

        for (int i = 0; i < attempts; i++) {
            if (!Files.exists(path)) return null;

            long size1;
            try {
                size1 = Files.size(path);
            } catch (IOException e) {
                sleepQuietly(interval);
                continue;
            }

            sleepQuietly(interval);

            if (!Files.exists(path)) return null;

            long size2;
            try {
                size2 = Files.size(path);
            } catch (IOException e) {
                continue;
            }

            if (size1 == size2 && size1 > 0) {
                return readWithRetry(path);
            }
        }
        log.warn("文件未稳定，跳过: {}", path.getFileName());
        return null;
    }

    /**
     * 重复读取文件
     *
     * @param path 文件路径
     * @return 文件内容
     */
    private String readWithRetry(Path path) {
        for (int i = 0; i <= props.getReadRetryCount(); i++) {
            try {
                // 尝试加共享读锁
                try (FileChannel ch = FileChannel.open(path, StandardOpenOption.READ);
                     FileLock lock = ch.tryLock(0, Long.MAX_VALUE, true)) {
                    if (lock == null) {
                        sleepQuietly(props.getReadRetryDelayMs());
                        continue;
                    }

                    List<String> fileContent = FileUtil.readLines(path.toFile(), Charset.defaultCharset());

                    return CollUtil.join(fileContent, StrUtil.CRLF);
                }
            } catch (NoSuchFileException e) {
                log.debug("文件在读取时被删除: {}", path.getFileName());
                return null;
            } catch (IOException e) {
                if (i < props.getReadRetryCount()) {
                    sleepQuietly(props.getReadRetryDelayMs());
                } else {
                    log.warn("多次重试后仍无法读取: {}", path.getFileName());
                }
            }
        }
        return null;
    }

    /**
     * 文件是否已经处理结束
     *
     * @param content
     * @return
     */
    private boolean isNotProcessed(String content) {
        String hash = DigestUtils.md5Hex(content);
        if (processedContentHashes.contains(hash)) {
            log.debug("内容已处理，跳过: {}...", content.substring(0, Math.min(16, content.length())));
            return false;
        }
        processedContentHashes.add(hash);
        return true;
    }

    private void sleepQuietly(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @PreDestroy
    public void shutdown() {
        log.info("正在关闭文件监控...");
        running = false;
        if (watchService != null) {
            try {
                watchService.close();
            } catch (IOException e) {
                log.warn("关闭 WatchService 失败", e);
            }
        }
        log.info("文件监控已关闭...");
    }


    /**
     * 上报给mes
     */
    private void report2MesV2(Path filePath) {
        String content = readLatestTrustedContent().orElse(null);

        if (StrUtil.isBlank(content)) {
            return;
        }
        List<String> lines = Arrays.asList(content.split(StrUtil.CRLF));
        JSONArray pcsSns = SpiFileParsePanelUtil.extractBoardBarcodeAsJson(lines);
        log.info("pcsSns: {}", pcsSns);
        Set<Integer> pcsNums = new HashSet<>();
        String productionSns = "";
        if (!pcsSns.isEmpty()) {
            productionSns = pcsSns.stream()
                    .map(obj -> (JSONObject) obj)
                    .sorted(Comparator.comparingInt(obj -> Integer.parseInt(obj.getStr("PcsNum"))))
                    .map(obj -> obj.getStr("PcsSn"))
                    .collect(Collectors.joining(","));
        }

        //文件备份
        backUpFile(filePath);

        String workStationCode = SpringContextHolder.getProperty("one.eap.work_station_code", "");
        String request = StrUtil.format(OneCommandConstant.ONE_PANEL_RELATION_INFO, productionSns, workStationCode);

        log.info("send to mes: request: \n {}", request);
        // 需要发送
        for (int i = 0; i < 3; i++) {
            try {
                //  上报数据给eap
                JSONObject response = mesInterface.submitReport(new JSONObject(request));
                log.info("Get message from mes: \n {}", JSONUtil.toJsonPrettyStr(response));
                if (response.containsKey("code")) {
                    int resCode = response.getInt("code");
                    if (resCode != 1) {
                        log.error("Get mes return error code :{}, msg:{}", resCode, response.get("msg"));
                    }
                    log.info("Request ok.");
                    break;
                } else {
                    log.error("Get mes return error, code is not exist.");
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 文件备份
     *
     * @param filePath 文件路径
     */
    private static void backUpFile(Path filePath) {
        log.info("开始备份文件：{} --> {}.", filePath.toString(), MES_BACKUP_DIR + filePath.getFileName());
        // 发送成功，无论返回值是什么，都要存到另一个文件夹下
        File destFile = new File(MES_BACKUP_DIR + filePath.getFileName());
        // 复制 文件
        FileUtil.copy(filePath.toFile(), destFile, true); // true 表示覆盖目标文件（如果存在）
    }

    /**
     * 上报给mes
     *
     * @param fileName 文件名称
     */
    private void report2Mes(String fileName) {
        try {
            String programName = fileName.split("_")[2];

            PanelRelationReportRecord record = panelRelationReportRecordService.getOne(
                    new LambdaQueryWrapper<PanelRelationReportRecord>()
                            .eq(PanelRelationReportRecord::getProgramName, programName).last(" limit 1 "));

            String content = readLatestTrustedContent().orElse(null);

            if (StrUtil.isBlank(content)) {
                return;
            }
            List<String> lines = Arrays.asList(content.split(StrUtil.CRLF));
            JSONArray pcsSns = SpiFileParseUtil.extractBoardBarcodeAsJson(lines);
            log.info("pcsSns: {}", pcsSns);

            String pcsSnsJson = JSONUtil.toJsonStr(pcsSns);

            boolean needSubmit = true;

            if (ObjectUtil.isNull(record)) {
                record = new PanelRelationReportRecord();
                record.setProgramName(programName);
                record.setFileName(fileName);
            } else {
                String oldJson = record.getReportData();

                // 没有变化，就不发送给mes
                if (LooseJsonComparator.equalsLoose(oldJson, pcsSnsJson)) {
                    log.info("数据库集合中存在程序{}, 并且数据无变化，继续检查上次是否发送成功。", programName);

                    if (!"1".equals(record.getIsReport())) {
                        log.info("上次发送不成功，继续发送。");
                    } else {
                        needSubmit = false;
                        log.info("上次发送成功，不继续发送。");
                    }
                }

                record.setFileName(fileName);
            }

            // 设置修改时间
            record.setModifyTime(DateUtil.now());
            record.setReportData(pcsSnsJson);

            // 需要发送
            if (needSubmit) {
                try {
                    String machineCode = SpringContextHolder.getProperty("one.eap.machine_code", "");
                    String macAddr = SpringContextHolder.getProperty("one.eap.mac_addr", "");

                    JSONObject pcsList = new JSONObject().set("PcsList", pcsSns);

                    String request = StrUtil.format(OneCommandConstant.ONE_PRODUCTION_INFO,
                            RandomUtil.randomNumbers(15), machineCode, macAddr,
                            DateUtil.date().toString(DatePattern.NORM_DATETIME_MS_PATTERN),
                            programName,
                            StringEscapeUtils.escapeJson(JSONUtil.toJsonStr(pcsList)));

                    log.info("send to mes: request: \n {}", request);

                    //  上报数据给eap
                    JSONObject response = mesInterface.submitReport(new JSONObject(request));

                    if (response.containsKey("Data")) {
                        JSONObject resJson = response.getJSONObject("Data");
                        String code = resJson.getStr("Code");

                        if (!"0".equals(code)) {
                            record.setIsReport("0");
                            // 接口异常，未上报成功
                        } else {
                            record.setIsReport("1");
                        }
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                // 不需要发送，则不处理数据库直接退出。
                return;
            }

            if (ObjectUtil.isNull(record.getId())) {
                // 保存到数据库
                panelRelationReportRecordService.save(record);
            } else {
                // 更新数据库
                panelRelationReportRecordService.updateById(record);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}